package co.yixiang.yshop.module.product.service.productcoupon;

import cn.hutool.core.util.IdUtil;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.module.product.controller.admin.productcoupon.vo.*;
import co.yixiang.yshop.module.product.convert.storeproductcoupon.StoreProductCouponConvert;
import co.yixiang.yshop.module.product.convert.storeproductcouponcdkey.StoreProductCouponCdKeyConvert;
import co.yixiang.yshop.module.product.convert.storeproductcouponrelation.StoreProductCouponRelationConvert;
import co.yixiang.yshop.module.product.dal.dataobject.productcoupon.ProductCouponCdKeyDO;
import co.yixiang.yshop.module.product.dal.dataobject.productcoupon.ProductCouponDO;
import co.yixiang.yshop.module.product.dal.dataobject.productcouponrelation.ProductCouponRelationDO;
import co.yixiang.yshop.module.product.dal.mysql.productcoupon.ProductCouponCdkeyMapper;
import co.yixiang.yshop.module.product.dal.mysql.productcoupon.ProductCouponMapper;
import co.yixiang.yshop.module.product.dal.mysql.productcouponrelation.ProductCouponRelationMapper;
import co.yixiang.yshop.module.product.enums.common.PageTypeEnum;
import co.yixiang.yshop.module.product.enums.coupon.*;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jodd.util.StringUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static co.yixiang.yshop.module.product.enums.ErrorCodeConstants.*;


/**
 * 商品优惠券 Service 实现类
 *
 * @author moxiangrong
 */
@Service
@Validated
public class ProductCouponServiceImpl extends ServiceImpl<ProductCouponMapper, ProductCouponDO> implements ProductCouponService {
    @Resource
    private ProductCouponRelationMapper productCouponRelationMapper;

    @Resource
    private ProductCouponCdkeyMapper productCouponCdkeyMapper;
    private final Lock lock = new ReentrantLock();


    @Override
    @Transactional
    public Long createCoupon(ProductCouponCreateReqVO vo) {
        ProductCouponDO couponDO = StoreProductCouponConvert.INSTANCE.convert(vo);
        if (vo.getIds() != null && vo.getIds().length > 0) {
            couponDO.setScopeValues(convertString(vo.getIds()));
        }
        Long totalNumber = couponDO.getTotalNumber();
        //优惠券剩余数量
        couponDO.setNumber(totalNumber);
        this.save(couponDO);
        Integer getType = vo.getGetType();
        Long couponDOId = couponDO.getId();
        switch (CouponGetTypeEnum.toType(getType)) {
            case NO_CODE:
                break;
            case COMMON_CODE:
                buildCouponCdKey(couponDOId, getType, vo.getCdkey());
                break;
            case ONE_2_ONE_CODE:
                buildCouponCdKey(couponDOId, getType, totalNumber);
                break;
        }
        return couponDOId;
    }

    /**
     * 检查兑换码,发码
     */
    public void buildCouponCdKey(Long couponId, Integer type, long total) {
        int max = 15;
        int min = 6;
        int random = new SecureRandom().nextInt(max - min + 1) + min;
        for (int i = 0; i < total; i++) {
            String code = IdUtil.fastSimpleUUID().substring(0, random);
            while (couponCdKeyExist(code)) {
                code = IdUtil.fastSimpleUUID().substring(0, random);
            }
            insertCdKey(couponId, type, code);
        }
    }

    /**
     * 检查兑换码
     */
    public void buildCouponCdKey(Long couponId, Integer type, String code) {
        if (couponCdKeyExist(code)) {
            throw exception(COUPON_CD_KEY_BAD_ERROR);
        }
        insertCdKey(couponId, type, code);
    }

    /**
     * 保存优惠券兑换码
     */
    public int insertCdKey(Long couponId, Integer type, String code) {
        ProductCouponCdKeyDO build = ProductCouponCdKeyDO.builder()
                .couponId(couponId)
                .type(type)
                .code(code)
                .build();
        return productCouponCdkeyMapper.insert(build);
    }

    /**
     * 优惠码是否已存在
     *
     * @return true:存在
     */
    public Boolean couponCdKeyExist(String code) {
        ProductCouponCdKeyDO productCouponCdKeyDO = productCouponCdkeyMapper.selectOne(
                Wrappers.<ProductCouponCdKeyDO>lambdaQuery()
                        .eq(ProductCouponCdKeyDO::getCode, code)
        );
        return productCouponCdKeyDO != null;
    }


    @Override
    @Transactional
    public Boolean updateCoupon(ProductCouponUpdateReqVO vo) {
        boolean result = true;
        ProductCouponDO productCouponDO = baseMapper.selectById(vo.getId());
        Integer expirationType = productCouponDO.getExpirationType();
        CouponStatusEnum statusEnum = null;
        if (CouponExpirationEnum.BY_DAY.getValue().equals(expirationType) ||
                CouponExpirationEnum.BY_PERMANENT.getValue().equals(expirationType)) {
            statusEnum = CouponStatusEnum.IN_PROGRESS;
        } else {
            LocalDateTime now = LocalDateTime.now();
            LocalDateTime takingEffectTime = productCouponDO.getTakingEffectTime();
            LocalDateTime expirationTime = productCouponDO.getExpirationTime();
            if (now.isBefore(takingEffectTime)) {
                statusEnum = CouponStatusEnum.NOT_STARTED;
            } else if (now.isAfter(takingEffectTime) && now.isBefore(expirationTime)) {
                statusEnum = CouponStatusEnum.IN_PROGRESS;
            } else {
                statusEnum = CouponStatusEnum.ENDED;
            }
        }
        switch (statusEnum) {
            case ENDED:
                throw exception(COUPON_STATUS_OUT_ERROR);
            case NOT_STARTED:
                ProductCouponDO couponDO = StoreProductCouponConvert.INSTANCE.convert(vo);
                if (vo.getIds() != null && vo.getIds().length > 0) {
                    couponDO.setScopeValues(convertString(vo.getIds()));
                }
                couponDO.setNumber(vo.getTotalNumber());
                result = updateById(couponDO);
                break;
            case IN_PROGRESS:
                //新的数量
                Long totalNumberNew = vo.getTotalNumber();
                //旧的总数
                Long totalNumberOld = productCouponDO.getTotalNumber();
                if (totalNumberNew < totalNumberOld) {
                    throw exception(COUPON_NUMBER_ERROR);
                }
                long addNum = 0;
                if (totalNumberNew > totalNumberOld) {
                    try {
                        lock.lock();
                        ProductCouponDO coupon = baseMapper.selectById(vo.getId());
                        addNum = totalNumberNew - coupon.getTotalNumber();
                        coupon.setTotalNumber(totalNumberNew);
                        coupon.setNumber(coupon.getNumber() + addNum);
                        baseMapper.updateById(coupon);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                    Integer getType = productCouponDO.getGetType();
                    //额外发券
                    if (getType.equals(CouponGetTypeEnum.ONE_2_ONE_CODE.getValue())) {
                        buildCouponCdKey(productCouponDO.getId(), getType, addNum);
                    }
                }
                break;
        }

        return result;
    }

    @Override
    public ProductCouponDetailRespVO getCouponDetail(Long id) {
        ProductCouponDO couponDO = getById(id);
        if (StringUtil.isNotBlank(couponDO.getScopeValues())) {
            couponDO.setScopeValues(convertArr(couponDO.getScopeValues()));
        }
        ProductCouponDetailRespVO detailRespVO = StoreProductCouponConvert.INSTANCE.convert(couponDO);
        Integer getType = detailRespVO.getGetType();
        if (getType.equals(CouponGetTypeEnum.COMMON_CODE.getValue())) {
            String code = productCouponCdkeyMapper.selectOne(
                    Wrappers.<ProductCouponCdKeyDO>lambdaQuery()
                            .eq(ProductCouponCdKeyDO::getCouponId, id)
            ).getCode();
            detailRespVO.setCdkey(code);
        }
        return detailRespVO;
    }

    @Override
    public PageResult<ProductCouponDetailRespVO> getCouponPage(StoreProductCouponPageReqVO pageReqVO) {
        PageResult<ProductCouponDetailRespVO> pageResult =
                StoreProductCouponConvert.INSTANCE.convertPage(this.baseMapper.selectPage(pageReqVO));
        pageResult.getList().forEach(res -> {
            Integer expirationType = res.getExpirationType();
            if (CouponExpirationEnum.BY_DAY.getValue().equals(expirationType) || CouponExpirationEnum.BY_PERMANENT.getValue().equals(expirationType)) {
                res.setStatus(CouponStatusEnum.IN_PROGRESS.getDesc());
            } else {
                LocalDateTime now = LocalDateTime.now();
                LocalDateTime takingEffectTime = res.getTakingEffectTime();
                LocalDateTime expirationTime = res.getExpirationTime();
                if (now.isBefore(takingEffectTime)) {
                    res.setStatus(CouponStatusEnum.NOT_STARTED.getDesc());
                } else if (now.isAfter(takingEffectTime) && now.isBefore(expirationTime)) {
                    res.setStatus(CouponStatusEnum.IN_PROGRESS.getDesc());
                } else {
                    res.setStatus(CouponStatusEnum.ENDED.getDesc());
                }
            }
            Long id = res.getId();
            Long count = productCouponRelationMapper.selectCount(Wrappers.<ProductCouponRelationDO>lambdaQuery()
                    .eq(ProductCouponRelationDO::getCouponId, id)
                    .eq(ProductCouponRelationDO::getIsUsed, 1)
            );
            //已使用数量
            res.setUsedNum(count);
        });
        return pageResult;
    }

    @Override
    public Boolean deleteCoupon(Long id) {
        return removeById(id);
    }

    @Override
    public PageResult<CanvasProductCouponRespVO> page(CanvasProductCouponPageReqVO pageReqVO) {
        Long loginUserId = getLoginUserId();
        if (PageTypeEnum.NOT_PAGE.getValue().equals(pageReqVO.getIsPage())) pageReqVO.setPageSize(Integer.MAX_VALUE);
        PageResult<CanvasProductCouponRespVO> result = StoreProductCouponConvert.INSTANCE.convertCanvasPage(
                this.baseMapper.getCouponListPage(pageReqVO)
        );
        if (loginUserId != null) {
            List<CanvasProductCouponRespVO> collect = result.getList().stream().peek(item -> {
                //优惠券id
                Long id = item.getId();
                long couponNum = productCouponRelationMapper.selectCount(
                        Wrappers.<ProductCouponRelationDO>lambdaQuery()
                                .eq(ProductCouponRelationDO::getUid, loginUserId)
                                .eq(ProductCouponRelationDO::getCouponId, id)
                );
                Integer receiveType = item.getReceiveType();
                if (receiveType == 1) {
                    item.setObtainable(Boolean.TRUE);
                } else {
                    long limitNumber = item.getLimitNumber();
                    item.setObtainable(limitNumber > couponNum);
                }
            }).collect(Collectors.toList());
            return result.setList(collect);
        }
        return result;
    }

    private String convertString(String[] arr) {
        return String.join(",", arr);
    }

    private String convertArr(String value) {
        return "[" + value + "]";
    }

    @Override
    public PageResult<ProductCouponIssueRecordRespVO> couponIssueRecord(ProductCouponIssueRecordReqVO pageVO) {
        Long couponId = pageVO.getCouponId();
        //优惠券主体
        ProductCouponDO coupon = baseMapper.selectOne(Wrappers.<ProductCouponDO>lambdaQuery()
                .eq(ProductCouponDO::getId, couponId)
        );
        Integer getType = coupon.getGetType();
        String couponName = coupon.getCouponName();
        LocalDateTime createTime = coupon.getCreateTime();

        PageResult<ProductCouponIssueRecordRespVO> page = null;

        switch (CouponGetTypeEnum.toType(getType)) {
            case NO_CODE:
                page = couponRelationRecord(pageVO);
                if (page.getList() != null) {
                    page.setList(this.peekList(page.getList(), coupon, couponName, createTime, null));
                }
                break;
            case COMMON_CODE:
                page = couponRelationRecord(pageVO);
                String cdKey = getCommentCdKey(couponId);
                if (page.getList() != null) {
                    page.setList(this.peekList(page.getList(), coupon, couponName, createTime, cdKey));
                }
                break;
            case ONE_2_ONE_CODE:
                page = recordCdKey(pageVO);
                if (page.getList() != null) {
                    page.setList(this.peekList(page.getList(), coupon, couponName, null, null));
                }
                break;
        }
        return page;
    }


    private PageResult<ProductCouponIssueRecordRespVO> couponRelationRecord(ProductCouponIssueRecordReqVO pageVO) {
        PageResult<ProductCouponRelationDO> page = productCouponRelationMapper.selectPage(pageVO,
                Wrappers.<ProductCouponRelationDO>lambdaQuery()
                        .eq(ProductCouponRelationDO::getCouponId, pageVO.getCouponId())
        );
        PageResult<ProductCouponIssueRecordRespVO> result = StoreProductCouponRelationConvert.INSTANCE.convertPage(page);
        List<ProductCouponIssueRecordRespVO> collect = result.getList().stream().peek(item -> {
            Long uid = item.getUid();
            item.setUsername(productCouponRelationMapper.selectUName(uid));
        }).collect(Collectors.toList());
        result.setList(collect);
        return result;
    }

    private String getCommentCdKey(Long couponId) {
        return productCouponCdkeyMapper.selectOne(
                Wrappers.<ProductCouponCdKeyDO>lambdaQuery()
                        .eq(ProductCouponCdKeyDO::getCouponId, couponId)
                        .last("limit 1")
        ).getCode();
    }

    @Override
    public List<ProductCouponIssueRecordRespVO> exportRecordCdKey(ProductCouponIssueRecordReqVO pageVO) {
        Long couponId = pageVO.getCouponId();
        //优惠券主体
        ProductCouponDO coupon = baseMapper.selectOne(Wrappers.<ProductCouponDO>lambdaQuery()
                .eq(ProductCouponDO::getId, couponId)
        );
        String couponName = coupon.getCouponName();
        pageVO.setPageSize(Integer.MAX_VALUE);
        PageResult<ProductCouponIssueRecordRespVO> page = recordCdKey(pageVO);
        page.setList(this.peekList(page.getList(), coupon, couponName, null, null));
        return page.getList();
    }


    private PageResult<ProductCouponIssueRecordRespVO> recordCdKey(ProductCouponIssueRecordReqVO pageVO) {
        PageResult<ProductCouponCdKeyDO> page = productCouponCdkeyMapper.selectPage(pageVO,
                Wrappers.<ProductCouponCdKeyDO>lambdaQuery()
                        .eq(ProductCouponCdKeyDO::getCouponId, pageVO.getCouponId())
                        .orderByDesc(ProductCouponCdKeyDO::getCreateTime)
        );
        PageResult<ProductCouponIssueRecordRespVO> result = StoreProductCouponCdKeyConvert.INSTANCE.convertPage(page);
        List<ProductCouponIssueRecordRespVO> collect = result.getList().stream().peek(item -> {
            ProductCouponRelationDO relation = productCouponRelationMapper.selectOne(
                    Wrappers.<ProductCouponRelationDO>lambdaQuery()
                            .eq(ProductCouponRelationDO::getCdKeyId, item.getId())
            );
            if (relation != null) {
                item.setIsUsed(relation.getIsUsed());
                item.setReceiveTime(relation.getReceiveTime());
                item.setUsageTime(relation.getUsageTime());
                item.setUsername(productCouponRelationMapper.selectUName(relation.getUid()));
            }
        }).collect(Collectors.toList());
        result.setList(collect);
        return result;
    }

    private List<ProductCouponIssueRecordRespVO> peekList(List<ProductCouponIssueRecordRespVO> list, ProductCouponDO coupon,
                                                          String couponName, LocalDateTime createTime, String cdKey) {
        return list.stream().peek(item -> {
            item.setCouponName(couponName);
            if (createTime != null) {
                item.setCreateTime(createTime);
            }
            if (cdKey != null) {
                item.setCode(cdKey);
            }
            //未领取，已领取（待使用），已使用，已过期  删除
            Integer isUsed = item.getIsUsed();
            Integer status = item.getStatus();
            String state = null;
            if (coupon.getGetType() != CouponGetTypeEnum.ONE_2_ONE_CODE.getValue()) {
                //通过 isUsed 判断状态
                if (isUsed.equals(CouponRefStatusEnum.NO_USE.getValue())) {
                    Integer expirationType = coupon.getExpirationType();
                    LocalDateTime now = LocalDateTime.now();
                    switch (CouponExpirationEnum.toType(expirationType)) {
                        case BY_TIME:
                            LocalDateTime expirationTime = coupon.getExpirationTime();
                            state = now.isAfter(expirationTime) ?
                                    CouponRefStatusEnum.OUT_TIME.getDesc() : CouponRefStatusEnum.NO_USE.getDesc();
                            break;
                        case BY_DAY:
                            Long expirationDay = coupon.getExpirationDay();
                            LocalDateTime receiveTime = item.getReceiveTime();
                            state = now.isAfter(receiveTime.plusDays(expirationDay))
                                    ? CouponRefStatusEnum.OUT_TIME.getDesc() : CouponRefStatusEnum.NO_USE.getDesc();
                            break;
                        case BY_PERMANENT:
                            state = CouponRefStatusEnum.NO_USE.getDesc();
                            break;
                    }
                } else {
                    state = CouponRefStatusEnum.USE.getDesc();
                }
            } else {
                //一卡一码
                state = CouponCdKeyStatusEnum.toType(status).getDesc();
            }
            item.setStateName(state);
        }).collect(Collectors.toList());
    }

    @Override
    public void deleteCouponCdKey(Long[] ids) {
        ProductCouponCdKeyDO build = ProductCouponCdKeyDO.builder()
                .status(CouponCdKeyStatusEnum.DEL.getValue())
                .build();
        for (Long id : ids) {
            ProductCouponCdKeyDO productCouponCdKeyDO = productCouponCdkeyMapper.selectById(id);
            if (CouponCdKeyStatusEnum.NO_USE.getValue().equals(productCouponCdKeyDO.getStatus())) {
                productCouponCdkeyMapper.update(build, Wrappers.<ProductCouponCdKeyDO>lambdaQuery()
                        .eq(ProductCouponCdKeyDO::getId, id));
            }
        }
    }

    @Override
    public ProductCouponDO findById(Long id) {
        return baseMapper.selectById(id);
    }

    @Override
    public int updateCouponById(ProductCouponDO couponDO) {
        return baseMapper.updateById(couponDO);
    }

    public Boolean couponCheckAndUpdate(Long id, Long userId, Long cdKeyId, long count) {
        ProductCouponDO currCouponDO = findById(id);
        if (currCouponDO == null) {
            throw exception(PRODUCT_COUPON_NOT_EXISTS);
        }
        if (currCouponDO.getNumber() < 1) throw exception(NOT_PRODUCT_COUPON_ERROR);
        if (CouponReceiveTypeEnum.LIMITATION.getValue().equals(currCouponDO.getReceiveType())
                && count >= currCouponDO.getLimitNumber()) {
            throw exception(PRODUCT_COUPON_RECEIVE_ERROR);
        }
        boolean result = false;
        try {
            lock.lock();
            // 查询优惠券
            ProductCouponDO couponDO = findById(id);
            if (couponDO == null) {
                throw exception(PRODUCT_COUPON_NOT_EXISTS);
            }
            if (couponDO.getNumber() < 1) throw exception(NOT_PRODUCT_COUPON_ERROR);

            if (CouponReceiveTypeEnum.UNLIMITED.getValue().equals(couponDO.getReceiveType())
                    || count < couponDO.getLimitNumber()) {
                result = updateById(ProductCouponDO.builder().id(couponDO.getId()).number(couponDO.getNumber() - 1).build());
            } else {
                throw exception(PRODUCT_COUPON_RECEIVE_ERROR);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return result;
    }
}
